using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Linq;

namespace PropertyExpression.ControllableQuery
{
    public static class DB
    {
        private static readonly ConcurrentDictionary<string, string> insertDictionary = new ConcurrentDictionary<string, string>();

        public static SubQuery Insert<T>(string tableName, T param)
        {
            return new SubQuery(writer => {
                string value;
                if (!insertDictionary.TryGetValue(tableName, out value))
                {
                    var list = GetColumns(tableName).ToList().Select(_ => _.Item1);
                    value = "INSERT INTO " + tableName + " ("
                        + string.Join(", ", list) + ") VALUES ("
                        + string.Join(", ", list.Select(_ => "@" + _)) + ")";
                    insertDictionary[tableName] = value;
                }
                writer.WriteLiteral(value);
                writer.AppendParams(param);
            });
        }

        private static readonly ConcurrentDictionary<string, string> updateDictionary = new ConcurrentDictionary<string, string>();

        public static SubQuery Update<T>(string tableName, T param)
        {
            return new SubQuery(writer => {
                string value;
                if (!updateDictionary.TryGetValue(tableName, out value))
                {
                    var list = GetColumns(tableName).ToList();
                    value = "UPDATE " + tableName + " SET "
                        + string.Join(", ", from _ in list where !_.Item2 select _.Item1 + " = @" + _.Item1)
                        + " WHERE "
                        + string.Join(" AND ", from _ in list where _.Item2 select _.Item1 + " = @" + _.Item1);
                    updateDictionary[tableName] = value;
                }
                writer.WriteLiteral(value);
                writer.AppendParams(param);
            });
        }

        private static readonly ConcurrentDictionary<string, string> deleteDictionary = new ConcurrentDictionary<string, string>();

        public static SubQuery Delete<T>(string tableName, T param)
        {
            return new SubQuery(writer => {
                string value;
                if (!deleteDictionary.TryGetValue(tableName, out value))
                {
                    value = "DELETE FROM " + tableName + " WHERE "
                        + string.Join(" AND ",
                            from _ in GetColumns(tableName) where _.Item2 select _.Item1 + " = @" + _.Item1);
                    deleteDictionary[tableName] = value;
                }
                writer.WriteLiteral(value);
                writer.AppendParams(param);
            });
        }

        public static Func<DbConnection> ConnectionFunc { private get; set; }

        private static IEnumerable<Tuple<string, bool>> GetColumns(string tableName)
        {
            using (var connection = ConnectionFunc())
            {
                connection.Open();
                using (var command = connection.CreateCommand())
                {
                    command.CommandText = "SELECT * FROM " + tableName;
                    using (var reader = command.ExecuteReader(CommandBehavior.KeyInfo))
                        foreach (DataRow dataRow in reader.GetSchemaTable().Rows)
                            yield return Tuple.Create(
                                (string) dataRow["ColumnName"],
                                true.Equals(dataRow["IsKey"]));
                }
            }
        }
    }
}